<?php
/**
 * Note: 网页授权获得auth_code, 然后获取accessToken, 查询子账户ID, 后续根据子账户ID查询数据
 */

namespace App\Services\Promo;

use Carbon\Carbon;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class UcService
{
    // protected const API_IS_STEWARD_ACCOUNT = 'https://e.uc.cn/shc/api/account/isStewardAccount'; // 获取当前账号是否为管家账号
    // protected const API_CHILDREN_ACCOUNT = 'https://e.uc.cn/shc/api/account/getChildrenAccountByAccountId'; // 管家账号下子账户
    // protected const API_CCID_USER = 'https://e.uc.cn/shc/api/account/getCcidUser'; // 获取同主体同代理的其他账号信息

    protected const API_GET_ACCOUNT = 'https://e.uc.cn/shc/api/account/getAccount'; // 获取username所对应的账户信息
    protected const API_REPORT_ACCOUNT = 'https://e.uc.cn/shc/api/report/account'; // 获取账户报告
    protected const API_REPORT_FILE_STATUS = 'https://e.uc.cn/shc/api/report/getFile'; // 查询下载任务的状态
    protected const API_REPORT_FILE_DOWNLOAD = 'https://e.uc.cn/shc/api/report/downloadFile'; // 下载数据报告

    // 智投账户用下列接口
    protected const API_ZHITOU_REPORT_ACCOUNT = 'https://e.uc.cn/sa-api/report/getDetailAsync'; // 获取账户报告 智投
    protected const API_ZHITOU_REPORT_FILE_STATUS = 'https://e.uc.cn/sa-api/report/getTaskState'; // 查询下载任务的状态 智投
    protected const API_ZHITOU_REPORT_FILE_DOWNLOAD = 'https://e.uc.cn/sa-api/report/downloadTaskResult'; // 下载数据报告 智投

    protected static $configs;

    public string $userName;

    /**
     * @param null $key
     * @return array|\Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed|null
     */
    protected function getConfig($key = null)
    {
        if (!self::$configs) self::$configs = collect(config('promo.uc'))->keyBy(function($row) {
            return $row['target'] ?: $row['username'];
        });

        return $key ? (self::$configs[$this->userName][$key] ?? null) : self::$configs[$this->userName];
    }

    public function get(string $key, $default = null)
    {
        $key = __CLASS__ . $key;
        return Cache::get($key, $default);
    }

    public function set(string $key, $val, $ttl = null)
    {
        $key = __CLASS__ . $key;
        Cache::put($key, $val, $ttl);
    }

    public function has(string $key): bool
    {
        $key = __CLASS__ . $key;
        return Cache::has($key);
    }
    /**
     * @param string $username
     * @return $this
     */
    public function setUserName(string $username): UcService
    {
        $this->userName = $username;
        return $this;
    }

    /**
     * 发送get请求
     * @param $url
     * @param $data
     * @return false|PromiseInterface|Response
     * @throws \Exception
     */
    protected function requestGet($url, $data)
    {
        // 发生错误时 100ms 后重试 1 次, 每次超时3s (设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw), 这里放入try
        try {
            info(__METHOD__, [$url, $data]);
            $response = Http::retry(2, 100)->timeout(3)->asForm()->get($url, $data);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception("百度正在维护中，请联系客服");
        }
        return $response;
    }

    /**
     * 发送post请求
     * @param string $url
     * @param array $data
     * @return false|PromiseInterface|Response
     * @throws \Exception
     */
    protected function requestPost(string $url, array $data)
    {
        // 发生错误时 100ms 后重试 1 次, 每次超时3s (设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw), 这里放入try
        try {
            info(__METHOD__, [$url, $data]);
            $response = Http::retry(2, 100)->timeout(3)->asJson()->post($url, $data);
            $response->throw();
            info(__METHOD__, [$response]);
            if (isset($response['header']['status']) && $response['header']['status'] > 0) throw new \Exception($response['header']['desc']);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception($e->getMessage());
        }
        return $response;
    }

    /**
     * 获取当前账号是否为管家账号
     * @return bool
     * @throws \Exception
     */
    /*public function isStewardAccount(): bool
    {
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
        ];
        $response = $this->requestPost(self::API_IS_STEWARD_ACCOUNT, $param, []);
        if (!$response) return false;
        return $response['body']['isStewardAccount'] ?? false;
    }*/

    /**
     * 管家账号下子账户
     * @return array
     * @throws \Exception
     */
    /*public function childrenAccount(): array
    {
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "agentType" => 1,
            ],
        ];
        $response = $this->requestPost(self::API_CHILDREN_ACCOUNT, $param, []);
        if (!$response) return [];
        $lists = $response['body']['childrenAccounts'] ?? [];
        // 处理下与
        return array_map(function($v) {
            return [
                'userId' => $v['id'],
                'userName' => $v['name'],
            ];
        }, $lists);
    }*/

    /**
     * 获取同主体同代理的其他账号信息
     * @return array
     * @throws \Exception
     */
   /*public function getCcidUser(): array
    {
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
        ];
        $response = $this->requestPost(self::API_CCID_USER, $param, []);
        if (!$response) return [];
        return $response['body']['simpleUserTypes'] ?? [];
    }*/

    public function getAccountUsername(): string
    {
        if ($target = $this->getConfig('target')) return $target;
        return $this->getConfig('username');
    }

    /**
     * 获取账户信息
     * @return false
     * @throws \Exception
     */
    public function getAccount()
    {
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ]
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        $response = $this->requestPost(self::API_GET_ACCOUNT, $param, []);
        if (!$response) throw new \Exception("获取账户信息失败");
        return $response['body']['accountInfoType'] ?? false;
    }

    /**
     * 是否是智投账户
     * @return bool
     * @throws \Exception
     */
    public function isZhiTouAccount(): bool
    {
        $key = "zhitou:{$this->getAccountUsername()}";
        if (self::has($key)) return self::get($key);

        $account = $this->getAccount();
        $ok = $account['bizType'] === 2;
        self::set($key, $ok, now()->addWeek());
        return $ok;
    }

    /**
     * 获取账户报告
     * @param array $timeRange
     * @return int
     * @throws \Exception
     */
    public function reportAccount(array $timeRange = []): int
    {
        if ($this->isZhiTouAccount()) return $this->zhitouReportAccount($timeRange);

        $timeRange = array_map(function ($v) {
            if ($v instanceof Carbon) return $v->format('Y-m-d');
            return $v;
        }, $timeRange);

        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "unitOfTime" => 1,
                "startDate" => $timeRange[0],
                "endDate" => $timeRange[1],
                "performanceData" => [
                    "consume",
                    "sevenDayConsume",
                ],
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        $response = $this->requestPost(self::API_REPORT_ACCOUNT, $param, []);
        if (!$response) return 0;
        return $response['body']['taskId'] ?? 0;
        // 花费 "taskId":399
    }

    /**
     * 获取账户报告 智投版
     * @param array $timeRange
     * @return int
     * @throws \Exception
     */
    public function zhitouReportAccount(array $timeRange = []): int
    {
        $timeRange = array_map(function ($v) {
            if ($v instanceof Carbon) return $v->format('Ymd');
            if (is_string($v)) return str_replace('-', '', $v);
            return $v;
        }, $timeRange);

        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                'reportType' => [
                    "adReportType" => 1,
                    "aggregateType" => 1,
                    "startDate" => (int) $timeRange[0],
                    "endDate" => (int) $timeRange[1],
                    "reportColumns" => [
                        "consume",
                        "sevenDayConsume",
                    ],
                ]
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        $response = $this->requestPost(self::API_ZHITOU_REPORT_ACCOUNT, $param, []);
        if (!$response) return 0;
        return $response['body']['taskId'] ?? 0;
        // 花费 "taskId":399
    }

    /**
     * 查询下载任务的状态
     * @param int $taskId
     * @return bool
     * @throws \Exception
     */
    public function reportFileStatus(int $taskId): bool
    {
        if ($this->isZhiTouAccount()) return $this->zhitouReportFileStatus($taskId);
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "taskId" => $taskId,
                "adReport" => true,
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        $response = $this->requestPost(self::API_REPORT_FILE_STATUS, $param, []);
        if (!$response) return false;
        return $response['body']['reportFileType']['ready'] ?? false;
        // 花费 "reportFileType":{
        //  taskId: 123,
        //  ready: false
        //}
    }

    /**
     * 查询下载任务的状态 智投版
     * @param int $taskId
     * @return bool
     * @throws \Exception
     */
    public function zhitouReportFileStatus(int $taskId): bool
    {
        $param = [
            'header' => [
                'username' =>$this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "taskId" => $taskId,
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        $response = $this->requestPost(self::API_ZHITOU_REPORT_FILE_STATUS, $param, []);
        if (!$response) return false;
        return $response['body']['isReady'] ?? false;
        // {
        //  taskId: 123,
        //  isReady: false
        //}
    }

    /**
     * 下载数据报告
     * @param int $taskId
     * @return array
     * @throws \Exception
     */
    public function reportDownloadFile(int $taskId): array
    {
        if ($this->isZhiTouAccount()) return $this->zhitouReportDownloadFile($taskId);
        $param = [
            'header' => [
                'username' => $this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "taskId" => $taskId,
                // "adReport" => true,
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        try {
            info(__METHOD__, [self::API_REPORT_FILE_DOWNLOAD, $param]);
            $response = Http::retry(2, 100)->timeout(3)->post(self::API_REPORT_FILE_DOWNLOAD, $param);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception($e->getMessage());
        }
        $output_string = iconv('gbk', 'utf-8', $response->body());
        if (!$output_string) return [];

        // 只有两行, 第一行表头, 第二行是数据
        $csv = array_map('str_getcsv', explode("\r\n", $output_string));
        $fields = array_shift($csv);

        $data = [];
        foreach ($csv as $row) {
            if (!$row) continue;
            foreach ($row as $rk => $rv) {
                $data[$fields[$rk]] = $rv;
            }
        }
        return $data;
    }

    /**
     * 下载数据报告
     * @param int $taskId
     * @return array
     * @throws \Exception
     */
    public function zhitouReportDownloadFile(int $taskId): array
    {
        $param = [
            'header' => [
                'username' => $this->getConfig('username'),
                'password' => $this->getConfig('password'),
                'token' => $this->getConfig('token'),
            ],
            'body' => [
                "taskId" => $taskId,
                // "adReport" => true,
            ],
        ];

        if ($target = $this->getConfig('target')) {
            $param['header']['target'] = $target;
        }
        try {
            info(__METHOD__, [self::API_ZHITOU_REPORT_FILE_DOWNLOAD, $param]);
            $response = Http::retry(2, 100)->timeout(3)->post(self::API_ZHITOU_REPORT_FILE_DOWNLOAD, $param);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception($e->getMessage());
        }
        $output_string = iconv('gbk', 'utf-8', $response->body());
        if (!$output_string) return [];

        // 只有两行, 第一行表头, 第二行是数据
        $csv = array_map('str_getcsv', explode("\r\n", $output_string));
        $fields = array_shift($csv);

        $data = [];
        foreach ($csv as $row) {
            if (!$row) continue;
            foreach ($row as $rk => $rv) {
                $data[$fields[$rk]] = $rv;
            }
        }
        return $data;
    }

    /**
     * 获取账户消费
     * TODO 只能通过中文键值关联数据, 文档中还没有
     * @param string $userName
     * @param string $day
     * @return float|int
     * @throws \Exception
     */
    public function getAccountCost(string $userName, string $day): array
    {
        // 过期时间
        $ttl = now()->addMinutes(4);

        // 查询账户报告, taskId有缓存优先缓存
        $key = sprintf("%s:%s:taskId", $userName, $day);
        $taskId = self::get($key);
        if (!$taskId) {
            $taskId = $this->setUserName($userName)->reportAccount([$day, $day]);
            self::set($key, $taskId, $ttl);
        }

        // 查询文件进度, taskId有缓存优先缓存
        $key = sprintf("%s:%s:fileStatus", $userName, $day);
        $status = self::get($key);
        if (!$status) {
            $status = $this->setUserName($userName)->reportFileStatus($taskId);
            if (!$status) return [0, 0];
            self::set($key, true, $ttl);
        }

        // 下载CSV
        $data = $this->setUserName($userName)->reportDownloadFile($taskId);
        info(__METHOD__, $data);
        $userId = $data['账户ID'] ?? 0;
        $consume = $data['消费']??0;
        $sevenDayConsume = $data['安心消费']??0;
        $cost = $consume - $sevenDayConsume;
        return [(int) $userId, $cost];
    }
}
